from abc import abstractmethod, ABCMeta
from contextlib import asynccontextmanager

from typing import Final, Set, Optional
from typing_extensions import Self

from bald_spider import Response
from bald_spider.middleware.middleware_manager import MiddlewareManager
from bald_spider.utils.log import get_logger


class ActiveRequestManager:
    def __init__(self):
        self._active: Final[Set] = set()

    @asynccontextmanager
    async def __call__(self, request):
        try:
            yield self.add(request)
        finally:
            self.remove(request)

    def add(self, request):
        self._active.add(request)

    def remove(self, request):
        self._active.remove(request)

    def __len__(self):
        return len(self._active)


class DownloaderMate(ABCMeta):
    def __subclasscheck__(self, subclass):
        required_method = {"create_instance", "fetch", "download", "closer", "idle"}
        is_subclass = all(
            hasattr(subclass, method) and callable(getattr(subclass, method, None)) for method in required_method
        )
        return is_subclass


class DownloaderBase(metaclass=DownloaderMate):
    def __init__(self, crawler):
        self.crawler = crawler
        self._active = ActiveRequestManager()
        self.middleware_manager : Optional[MiddlewareManager] = None
        self.logger = get_logger(self.__class__.__name__, crawler.settings.get('LOG_LEVEL'))

    @classmethod
    def create_instance(cls, *args, **kwargs) -> Self:
        return cls(*args, **kwargs)

    def open(self) -> None:
        self.logger.info(
            f"{self.crawler.spider} <downloader class: {type(self).__name__}>"
            f"<concurrency: {self.crawler.settings.get('CONCURRENCY')}>"
        )
        self.middleware_manager = MiddlewareManager(self.crawler)

    async def fetch(self, request) -> Optional[Response]:
        async with self._active(request):
            # response = await self.download(request)
            response = await self.middleware_manager.download(request)
            return response

    @abstractmethod
    async def download(self, request) -> Optional[Response]:
        pass

    def __len__(self) -> int:
        return len(self)

    def idle(self) -> bool:
        return len(self._active) == 0

    async def closer(self) -> None:
        pass
